I implemented RectCut in Haxe.
I've been working in Go/Typescript for the last few years, and most of my personal projects have been Go since then. Really a great language. But in the last few months I've been getting the eye back in with Haxe, finishing off some projects I started a long time ago. As part of one of them (which maybe I'll talk about some time), I was writing UI layout code. There's a lot of ways to approach this, and honestly, a lot of it is overkill when yo know the shape of what you're trying to accomplish. I'm not building a general purpose layout engine, I'm specifically building a handful of menus and UIs that fit pretty squarely into static-ish layouts. But they should still be relative, screen sizes change etc.
A few weeks back I read the RectCut article above. It's a smart idea - there's actually very little primitives needed to build a basic UI. Very inspired by immediate mode GUIs. And also a good fit for what I was writing. I spent ~an hour implementing it in Haxe. The code I'm sharing is library agnostic, but I'm using it with HaxeFlixel (crazy to return to Flixel 15 years after I last touched it). Haxe is still a great language - the Actionscript 3 follow on that everyone needs.
Copyright (c) 2025 Jack Gleeson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package;
typedef RectCutRectangle = {
var minX:Float;
var minY:Float;
var maxX:Float;
var maxY:Float;
}
class RectCut {
public static function GetRectangle(width:Float, height:Float, x:Float, y:Float):RectCutRectangle {
return {
minX: x - width / 2,
maxX: x + width / 2,
minY: y - height / 2,
maxY: y + height / 2
};
}
public static function CutLeft(rect:RectCutRectangle, width:Float):RectCutRectangle {
var minX = rect.minX;
rect.minX = Math.max(rect.minX, rect.minX + width);
return {
minX: minX,
minY: rect.minY,
maxX: rect.minX,
maxY: rect.maxY
};
}
public static function CutRight(rect:RectCutRectangle, width:Float):RectCutRectangle {
var maxX = rect.maxX;
rect.maxX = Math.min(rect.maxX, rect.maxX - width);
return {
minX: rect.maxX,
minY: rect.minY,
maxX: maxX,
maxY: rect.maxY
};
}
public static function CutTop(rect:RectCutRectangle, height:Float):RectCutRectangle {
var minY = rect.minY;
rect.minY = Math.max(rect.minY, rect.minY + height);
return {
minX: rect.minX,
minY: minY,
maxX: rect.maxX,
maxY: rect.minY
};
}
public static function CutBottom(rect:RectCutRectangle, height:Float):RectCutRectangle {
var maxY = rect.maxY;
rect.maxY = Math.min(rect.maxY, rect.maxY - height);
return {
minX: rect.minX,
minY: rect.maxY,
maxX: rect.maxX,
maxY: maxY
};
}
public static function AddTop(rect:RectCutRectangle, height:Float):RectCutRectangle {
var minY = rect.minY - height;
return {
minX: rect.minX,
minY: minY,
maxX: rect.maxX,
maxY: rect.minY
};
}
}
And here's a basic example of how I'm using it:
uiLayoutRect = RectCut.GetRectangle(120, 200, FlxG.width / 2, FlxG.height / 2);
topRect = RectCut.CutTop(uiLayoutRect, 60);
leftRect = RectCut.CutLeft(uiLayoutRect, 20);
rightRect = RectCut.CutRight(uiLayoutRect, 20);
bottomRect = RectCut.CutBottom(uiLayoutRect, 20);
var titleText = new FlxSprite();
titleText.loadGraphic(AssetPaths.menutitle__png, false, 750, 295);
titleText.scale.set(0.2, 0.2);
titleText.updateHitbox();
var diffX = titleText.width - (topRect.maxX - topRect.minX);
var rectCenterX = topRect.minX - (diffX / 2);
var rectCenterY = topRect.minY;
titleText.x = rectCenterX;
titleText.y = rectCenterY;
add(titleText);
var playRect = RectCut.CutTop(uiLayoutRect, 20);
playButton = new FlxButton(playRect.minX, playRect.minY, "Play", handlePlayClicked);
add(playButton);
// spacing - this can
RectCut.CutTop(uiLayoutRect, 4);
var exitRect = RectCut.CutTop(uiLayoutRect, 20);
var exitButton = new FlxButton(exitRect.minX, exitRect.minY, "Exit", handlePlayClicked);
add(exitButton);